
//#include "apd/manifestreader/manifest_reader.h"
#include "common/exception.h"
#include "executables_parser.h"
#include "config/execution_manifest.h"
#include "filesystem.h"
#include "nosal/log/logger.h"
#include "nosal/log/logging.h"

#include "logger.h"

namespace
{
netaos::nosal::log::Logger& GetLogger()
{
    static netaos::nosal::log::Logger& logger = netaos::nosal::log::CreateLogger("ExPa", "Executables Parser", netaos::nosal::log::LogLevel::kInfo);
    return logger;
}

const std::string kManifestFileName("MANIFEST");
const std::string kOptDirPath("opt");
const std::string kBinDirName("bin");
const std::string kEtcDirName("etc");

}  // namespace

namespace netaos {
namespace nosal {
namespace exec {
namespace internal {

ExecutablesParser::ExecutablesParser(const std::string& root_path,
    const std::string& update_config_file_path,
    const config::EnterExitTimeout& default_process_timeout)
    : list_of_executables_result_{ExecManifestParsingErrc::kDirectoryStructureCorrupt}
    , root_path_(root_path)
    , update_config_file_path_(root_path + update_config_file_path)
    , default_process_timeout_(default_process_timeout)
{ }

netaos::nosal::core::Result<std::vector<Executable>>&& ExecutablesParser::ParseDirectories()
{
    list_of_executables_result_
        = netaos::nosal::core::Result<std::vector<Executable>>{ExecManifestParsingErrc::kDirectoryStructureCorrupt};
    list_of_executables_.clear();
    list_of_executable_paths_.clear();
    executable_directories_.clear();

    GetLogger().LogInfo() << "Searching for directories with Executables.";

    const std::string applicationsDirectory = root_path_ + kOptDirPath;

    executable_directories_ = Filesystem::GetSubdirectories(applicationsDirectory);

    LoadUpdateConfigOptions();

    if (executable_directories_.empty()) {
        GetLogger().LogFatal() << "Found no directories with Executables.";
        return std::move(list_of_executables_result_);
    }

    LoadExecutablePaths();

    netaos::nosal::core::Result<void> load_executables_result = LoadExecutables();
    if (!load_executables_result) {
        list_of_executables_result_.EmplaceError(load_executables_result.Error());
        return std::move(list_of_executables_result_);
    }

    list_of_executables_result_.EmplaceValue(std::move(list_of_executables_));

    return std::move(list_of_executables_result_);
}

netaos::nosal::core::Result<void> ExecutablesParser::LoadExecutables() noexcept
{
    GetLogger().LogInfo() << "Creating Executables from the extracted paths.";

    list_of_executables_.reserve(list_of_executable_paths_.size());

    for (const auto pathTuple : list_of_executable_paths_) {
        std::string const applicationDir = std::get<0>(pathTuple);
        std::string const executablePath = std::get<1>(pathTuple);
        std::string const manifestPath = std::get<2>(pathTuple);

        config::ExecutionManifest executionManifest{manifestPath, default_process_timeout_};
        if (executionManifest.GetStartupConfigs().empty()) {
            GetLogger().LogError() << "Ignoring" << Filesystem::ExtractFileName(std::string(executablePath.c_str()))
                                   << "as it has no StartupConfigs in its Execution Manifest.";
            continue;
        }

        list_of_executables_.emplace_back(std::move(executionManifest), applicationDir, executablePath);
    }

    if (list_of_executables_.empty()) {
        GetLogger().LogError() << "Invalid configuration. No proper Executables found.";
        return netaos::nosal::core::Result<void>{ExecManifestParsingErrc::kDirectoryStructureCorrupt};
    }

    GetLogger().LogInfo() << "Found" << list_of_executables_.size() << "Executables.";
    return netaos::nosal::core::Result<void>{};
}

void ExecutablesParser::LoadExecutablePaths() noexcept
{
    list_of_executables_.reserve(executable_directories_.size());
    for (const auto& dir : executable_directories_) {
        // Look for binaries
        const std::string binDir(Filesystem::BuildPath(dir, kBinDirName));
        const std::vector<std::string> binaries = Filesystem::GetFiles(binDir);
        if (binaries.size() == 1U) {
            GetLogger().LogDebug() << "Found Binary in" << binDir;
        } else {
            GetLogger().LogError() << "Exactly one binary per folder is allowed. Skip" << binDir;
            continue;
        }

        // Look for Execution Manifests
        const std::string etcDir(Filesystem::BuildPath(dir, kEtcDirName));
        const std::vector<std::string> manifestFileNames = Filesystem::GetFilesStartingWith(etcDir, kManifestFileName);
        if (manifestFileNames.size() == 1U) {
            GetLogger().LogDebug() << "Found Execution Manifest in" << etcDir;
        } else {
            GetLogger().LogError() << "Exactly one Execution Manifest per folder is allowed. Skip" << etcDir << "and"
                                   << binDir;
            continue;
        }

        // put pair of binary path and Execution Manifest path into the list.
        list_of_executable_paths_.emplace_back(std::make_tuple(dir, binaries.front(), manifestFileNames.front()));
    }
}

void ExecutablesParser::LoadUpdateConfigOptions()
{
#if 0
    auto processesFileRes = apd::manifestreader::OpenManifest(std::string(update_config_file_path_));
    if (!processesFileRes) {
        GetLogger().LogError() << "Executables Parser failed to open UCM manifest at" << update_config_file_path_;
        return;
    }
    auto processesFile = std::move(processesFileRes).Value();

    constexpr auto kKey = "key";
    constexpr auto kProcessList = "value.string";
    constexpr auto kChecksum = "checksum";

    auto keyRes = processesFile->GetValue<std::string>(kKey);
    auto processListRes = processesFile->GetArray<std::string>(kProcessList);
    auto checksumRes = processesFile->GetValue<std::uint32_t>(kChecksum);

    if (!keyRes) {
        GetLogger().LogError() << "Executables Parser failed to read key from" << update_config_file_path_;
    } else {
        GetLogger().LogInfo() << "Executables Parser read key" << keyRes.Value() << "from" << update_config_file_path_;
    }

    if (!checksumRes) {
        GetLogger().LogError() << "Executables Parser failed to read checksum from" << update_config_file_path_;
    } else {
        GetLogger().LogInfo() << "Executables Parser read checksum" << checksumRes.Value() << "from" << update_config_file_path_;
    }

    if (!processListRes) {
        GetLogger().LogError() << "Executables Parser failed to read processes from" << update_config_file_path_;
        return;
    }

    std::vector<std::string> processesList = processListRes.ValueOr(std::vector<std::string>());
    for (auto process : processesList) {
        GetLogger().LogDebug() << "Found link to process directory" << process << "in" << update_config_file_path_;
        if (std::find_if(executable_directories_.begin(),
                executable_directories_.end(),
                [process](std::string s) { return s == process; })
            == executable_directories_.end()) {
            GetLogger().LogInfo() << "Executables Parser adds directory" << process << "to executableDirectories.";
            executable_directories_.emplace_back(std::string(process.data(), process.size()));
        }
    }
#endif
    return;
}

}  // namespace internal
}  // namespace exec
}  // namespace nosal
}  // namespace netaos
